package com.ruoyi.common.utils.opcua;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class OpcUaClientUtil {
    private static final Logger logger = LoggerFactory.getLogger(OpcUaClientUtil.class);
    // 连接超时时间（毫秒）
    private static final int CONNECTION_TIMEOUT = 10000;
    // 操作超时时间（毫秒）
    private static final int OPERATION_TIMEOUT = 5000;

    public static void main(String[] args) {
        // 服务器配置 - 建议从配置文件读取
        String serverIp = "192.168.0.195";
        int serverPort = 12688;

        // 验证IP地址有效性
        if (!isValidIpAddress(serverIp)) {
            logger.error("无效的服务器IP地址: {}", serverIp);
            return;
        }

        try {
            // 使用实际IP地址构建端点URL
            String endpointUrl = String.format("opc.tcp://%s:%d", serverIp, serverPort);
            logger.info("尝试连接到OPC UA服务器: {}", endpointUrl);

            // 测试网络连通性
            if (!isHostReachable(serverIp, serverPort)) {
                logger.error("无法连接到服务器 {}:{}，请检查网络和端口", serverIp, serverPort);
                return;
            }

            // 创建并连接客户端（传入实际IP用于替换localhost）
            OpcUaClient client = createClient(endpointUrl, serverIp);
            client.connect().get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);

            logger.info("客户端已成功连接到服务器: {}", endpointUrl);

            // 读取服务器时间节点 - 验证连接有效性
            readServerTime(client);

            // 节点操作示例 - 根据实际节点名称修改
            List<NodeId> nodeIds = Arrays.asList(
                    new NodeId(2, "1K-003"),
                    new NodeId(2, "1K-004"),
                    new NodeId(2, "1K-005"),
                    new NodeId(2, "1M-004"),
                    new NodeId(2, "1M-005"),
                    new NodeId(2, "1M-006")
                    // 可添加更多节点
            );

            // 批量读取节点值
            Map<NodeId, Object> values = batchReadNodeValues(client, nodeIds);
            logger.info("批量读取结果:");

            // 批量写入示例值
            Map<NodeId, Object> writeValues = new HashMap<>();
            writeValues.put(new NodeId(2, "1K-003"), 1);
            writeValues.put(new NodeId(2, "1K-004"), 1);
            writeValues.put(new NodeId(2, "1K-005"), 1);
            writeValues.put(new NodeId(2, "1M-004"), 216.1);
            writeValues.put(new NodeId(2, "1M-005"), 56.3f);
            writeValues.put(new NodeId(2, "1M-006"), 151);

            Map<NodeId, StatusCode> writeResults = batchWriteNodeValues(client, writeValues);
            logger.info("批量写入结果:");
            writeResults.forEach((nodeId, status) -> logger.info("节点 {} 的写入状态: {}", nodeId, status.isGood() ? "成功" : "失败"));

            // 再次批量读取以确认更新
            Map<NodeId, Object> updatedValues = batchReadNodeValues(client, nodeIds);
            logger.info("更新后的批量读取结果:");
            updatedValues.forEach((nodeId, value) -> logger.info("节点 {} 的值为: {}", nodeId, value));

            // 断开连接
            client.disconnect().get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
            logger.info("客户端已成功断开连接");

        } catch (Exception e) {
            logger.error("客户端操作失败", e);
        }
    }

    /**
     * 创建OPC UA客户端，修复端点地址（替换localhost为实际IP）
     *
     * @param endpointUrl 初始端点URL（含实际IP）
     * @param serverIp    服务器实际IP地址（用于替换localhost）
     */
    public static OpcUaClient createClient(String endpointUrl, String serverIp) throws Exception {
        EndpointDescription endpoint;

        try {
            // 尝试发现服务器端点
            List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(endpointUrl)
                    .get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);

            // 选择最合适的端点（优先选择无安全策略的端点）
            endpoint = endpoints.stream()
                    .filter(e -> SecurityPolicy.None.getUri().equals(e.getSecurityPolicyUri()))
                    .findFirst()
                    .orElse(endpoints.get(0));

            // 关键修复：将端点URL中的localhost/127.0.0.1替换为实际服务器IP
            String originalEndpointUrl = endpoint.getEndpointUrl();
            String correctedEndpointUrl = replaceLocalhostWithIp(originalEndpointUrl, serverIp);

            if (!originalEndpointUrl.equals(correctedEndpointUrl)) {
                logger.warn("端点地址修正: {} -> {}", originalEndpointUrl, correctedEndpointUrl);
                // 创建新的端点描述符，使用修正后的URL
                endpoint = new EndpointDescription(
                        correctedEndpointUrl,
                        endpoint.getServer(),
                        endpoint.getServerCertificate(),
                        endpoint.getSecurityMode(),
                        endpoint.getSecurityPolicyUri(),
                        endpoint.getUserIdentityTokens(),
                        endpoint.getTransportProfileUri(),
                        endpoint.getSecurityLevel()
                );
            }

            logger.info("使用端点: {}", endpoint.getEndpointUrl());
        } catch (Exception e) {
            logger.warn("端点发现失败，将直接使用提供的URL构建端点: {}", e.getMessage());

            // 直接构建端点，确保使用实际IP
            endpoint = new EndpointDescription(
                    endpointUrl,                  // 直接使用含实际IP的URL
                    null,
                    ByteString.NULL_VALUE,
                    MessageSecurityMode.None,
                    SecurityPolicy.None.getUri(),
                    new UserTokenPolicy[0],
                    "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary",
                    UByte.valueOf((short) 0)
            );
        }

        // 确保客户端使用实际IP地址连接
        OpcUaClientConfig config = OpcUaClientConfig.builder()
                .setEndpoint(endpoint)
                .setIdentityProvider(new AnonymousProvider())
                .setRequestTimeout(UInteger.valueOf(OPERATION_TIMEOUT))
                .setApplicationName(LocalizedText.english("RuoYi OPC UA Client"))
                .build();

        return OpcUaClient.create(config);
    }

    /**
     * 将URL中的localhost或127.0.0.1替换为实际IP
     */
    private static String replaceLocalhostWithIp(String url, String actualIp) {
        try {
            URI uri = new URI(url);
            String host = uri.getHost();

            // 如果主机是localhost或127.0.0.1，则替换为实际IP
            if (host != null && (host.equals("localhost") || host.equals("127.0.0.1"))) {
                // 构建新的URL
                return String.format("%s://%s:%d%s",
                        uri.getScheme(),
                        actualIp,
                        uri.getPort(),
                        uri.getPath() != null ? uri.getPath() : ""
                );
            }
        } catch (URISyntaxException e) {
            logger.warn("解析URL失败: {}", url, e);
        }
        return url;
    }

    /**
     * 读取服务器时间节点，验证连接有效性
     */
    public static void readServerTime(OpcUaClient client) {
        try {
            ReadValueId readValueId = new ReadValueId(
                    Identifiers.Server_ServerStatus_CurrentTime,
                    AttributeId.Value.uid(),
                    null,
                    QualifiedName.NULL_VALUE
            );
            List<ReadValueId> readValueIds = Collections.singletonList(readValueId);

            CompletableFuture<ReadResponse> future = client.read(
                    0.0,
                    TimestampsToReturn.Both,
                    readValueIds
            );

            ReadResponse response = future.get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
            DataValue dataValue = response.getResults()[0];

            if (dataValue.getStatusCode() != null && dataValue.getStatusCode().isGood()) {
                DateTime serverTime = (DateTime) dataValue.getValue().getValue();
                logger.info("服务器当前时间: {}", serverTime.getJavaDate());
            } else {
                logger.error("读取服务器时间失败: {}", dataValue.getStatusCode());
            }

        } catch (Exception e) {
            logger.error("读取服务器时间时发生错误", e);
        }
    }

    /**
     * 读取指定节点的值
     */
    public static Object readNodeValue(OpcUaClient client, NodeId nodeId) {
        try {
            ReadValueId readValueId = new ReadValueId(
                    nodeId,
                    AttributeId.Value.uid(),
                    null,
                    QualifiedName.NULL_VALUE
            );
            List<ReadValueId> readValueIds = Collections.singletonList(readValueId);

            CompletableFuture<ReadResponse> future = client.read(
                    0.0,
                    TimestampsToReturn.Both,
                    readValueIds
            );

            ReadResponse response = future.get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
            DataValue dataValue = response.getResults()[0];

            if (dataValue.getStatusCode() != null && dataValue.getStatusCode().isGood()) {
                Object value = dataValue.getValue().getValue();
                logger.info("节点 {} 的值为: {}", nodeId, value);
                return value;
            } else {
                logger.error("读取节点 {} 失败: {}", nodeId, dataValue.getStatusCode());
                return null;
            }

        } catch (Exception e) {
            logger.error("读取节点值时发生错误", e);
            return null;
        }
    }

    /**
     * 批量读取节点值
     */
    public static Map<NodeId, Object> batchReadNodeValues(OpcUaClient client, List<NodeId> nodeIds) {
        Map<NodeId, Object> results = new HashMap<>(nodeIds.size());

        if (nodeIds == null || nodeIds.isEmpty()) {
            logger.warn("节点ID列表为空，不执行批量读取");
            return results;
        }

        try {
            List<ReadValueId> readValueIds = new ArrayList<>();
            for (NodeId nodeId : nodeIds) {
                ReadValueId readValueId = new ReadValueId(
                        nodeId,
                        AttributeId.Value.uid(),
                        null,
                        QualifiedName.NULL_VALUE
                );
                readValueIds.add(readValueId);
            }

            CompletableFuture<ReadResponse> future = client.read(
                    0.0,
                    TimestampsToReturn.Both,
                    readValueIds
            );

            ReadResponse response = future.get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
            DataValue[] dataValues = response.getResults();

            for (int i = 0; i < nodeIds.size() && i < dataValues.length; i++) {
                NodeId nodeId = nodeIds.get(i);
                DataValue dataValue = dataValues[i];

                if (dataValue.getStatusCode() != null && dataValue.getStatusCode().isGood() && dataValue.getValue() != null) {
                    Object value = dataValue.getValue().getValue();
                    results.put(nodeId, value);
                } else {
                    logger.error("读取节点 {} 失败: {}", nodeId, dataValue.getStatusCode());
                    results.put(nodeId, null);
                }
            }

        } catch (Exception e) {
            logger.error("批量读取节点值时发生错误", e);
        }

        return results;
    }

    /**
     * 写入指定节点的值，自动处理类型转换
     */
    public static StatusCode writeNodeValue(OpcUaClient client, NodeId nodeId, Object value) {
        try {
            // 尝试先读取节点当前值，获取其数据类型
            Object currentValue = readNodeValue(client, nodeId);
            Object convertedValue = convertValueToMatchType(currentValue, value);

            WriteValue writeValue = new WriteValue(
                    nodeId,
                    AttributeId.Value.uid(),
                    null,
                    new DataValue(new Variant(convertedValue))
            );
            List<WriteValue> writeValues = Collections.singletonList(writeValue);

            CompletableFuture<WriteResponse> future = client.write(writeValues);
            WriteResponse response = future.get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
            StatusCode statusCode = response.getResults()[0];

            if (statusCode.isGood()) {
                logger.info("成功写入节点 {} 的值: {} (转换后类型: {})",
                        nodeId, convertedValue, convertedValue.getClass().getSimpleName());
            } else {
                logger.error("写入节点 {} 失败: {}", nodeId, statusCode);
            }

            return statusCode;

        } catch (Exception e) {
            logger.error("写入节点值时发生错误", e);
            return StatusCode.BAD;
        }
    }

    /**
     * 批量写入节点值
     */
    public static Map<NodeId, StatusCode> batchWriteNodeValues(OpcUaClient client, Map<NodeId, Object> nodeValues) {
        Map<NodeId, StatusCode> results = new HashMap<>(nodeValues.size());

        if (nodeValues == null || nodeValues.isEmpty()) {
            logger.warn("节点值映射为空，不执行批量写入");
            return results;
        }

        try {
            List<WriteValue> writeValues = new ArrayList<>();
            List<NodeId> nodeOrder = new ArrayList<>();

            for (Map.Entry<NodeId, Object> entry : nodeValues.entrySet()) {
                NodeId nodeId = entry.getKey();
                Object value = entry.getValue();

                // 尝试转换值类型以匹配节点
                Object currentValue = readNodeValue(client, nodeId);
                Object convertedValue = convertValueToMatchType(currentValue, value);

                WriteValue writeValue = new WriteValue(
                        nodeId,
                        AttributeId.Value.uid(),
                        null,
                        new DataValue(new Variant(convertedValue))
                );

                writeValues.add(writeValue);
                nodeOrder.add(nodeId);
            }

            CompletableFuture<WriteResponse> future = client.write(writeValues);
            WriteResponse response = future.get(OPERATION_TIMEOUT, TimeUnit.MILLISECONDS);
            StatusCode[] statusCodes = response.getResults();

            for (int i = 0; i < nodeOrder.size() && i < statusCodes.length; i++) {
                NodeId nodeId = nodeOrder.get(i);
                StatusCode statusCode = statusCodes[i];
                results.put(nodeId, statusCode);
            }

        } catch (Exception e) {
            logger.error("批量写入节点值时发生错误", e);
        }

        return results;
    }

    /**
     * 辅助方法：将值转换为与目标类型匹配
     */
    private static Object convertValueToMatchType(Object targetValue, Object sourceValue) {
        if (targetValue == null || sourceValue == null) {
            return sourceValue;
        }

        Class<?> targetClass = targetValue.getClass();
        Class<?> sourceClass = sourceValue.getClass();

        // 类型已匹配，无需转换
        if (targetClass.equals(sourceClass)) {
            return sourceValue;
        }

        // 数值类型转换
        try {
            if (targetClass == Integer.class && sourceValue instanceof Number) {
                return ((Number) sourceValue).intValue();
            } else if (targetClass == Float.class && sourceValue instanceof Number) {
                return ((Number) sourceValue).floatValue();
            } else if (targetClass == Double.class && sourceValue instanceof Number) {
                return ((Number) sourceValue).doubleValue();
            } else if (targetClass == Long.class && sourceValue instanceof Number) {
                return ((Number) sourceValue).longValue();
            } else if (targetClass == Short.class && sourceValue instanceof Number) {
                return ((Number) sourceValue).shortValue();
            } else if (targetClass == String.class) {
                return sourceValue.toString();
            }
        } catch (Exception e) {
            logger.warn("类型转换失败: {} -> {}", sourceClass.getSimpleName(), targetClass.getSimpleName(), e);
        }

        return sourceValue;
    }

    /**
     * 验证IP地址有效性
     */
    private static boolean isValidIpAddress(String ip) {
        try {
            return InetAddress.getByName(ip) != null;
        } catch (UnknownHostException e) {
            return false;
        }
    }

    /**
     * 检查主机和端口的可达性
     */
    private static boolean isHostReachable(String host, int port) {
        try {
            return InetAddress.getByName(host).isReachable(3000) && TestSocketConnection.test(host, port);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 内部类：测试端口连接
     */
    private static class TestSocketConnection {
        private static boolean test(String host, int port) {
            try (java.net.Socket socket = new java.net.Socket()) {
                socket.connect(new java.net.InetSocketAddress(host, port), 3000);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
    }
}
